<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>OpenLayers 几何图形编辑组件</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <style>
    :root {
      --primary: #4a6fa5;
      --secondary: #6d9dc5;
      --dark: #1e2a3a;
      --light: #f8f9fa;
      --success: #4caf50;
      --warning: #ff9800;
      --danger: #f44336;
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: linear-gradient(135deg, #1a2a6c, #2c3e50);
      color: #f0f0f0;
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      min-height: 100vh;
      padding: 20px;
      line-height: 1.6;
    }

    .container {
      max-width: 1400px;
      margin: 0 auto;
    }

    header {
      text-align: center;
      padding: 30px 0;
      margin-bottom: 20px;
    }

    h1 {
      font-size: 2.8rem;
      margin-bottom: 15px;
      background: linear-gradient(to right, #4facfe, #00f2fe);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }

    .subtitle {
      font-size: 1.2rem;
      opacity: 0.9;
      max-width: 700px;
      margin: 0 auto;
    }

    .app-container {
      display: grid;
      grid-template-columns: 1fr 350px;
      gap: 20px;
      height: calc(100vh - 200px);
    }

    #map {
      width: 100%;
      height: 100%;
      border-radius: 12px;
      overflow: hidden;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
    }

    .control-panel {
      background: rgba(30, 40, 60, 0.85);
      border-radius: 12px;
      padding: 20px;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
      backdrop-filter: blur(10px);
      border: 1px solid rgba(255, 255, 255, 0.1);
      overflow-y: auto;
      display: flex;
      flex-direction: column;
    }

    .panel-section {
      margin-bottom: 25px;
      padding-bottom: 20px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    }

    .panel-section h3 {
      font-size: 1.4rem;
      margin-bottom: 15px;
      color: #4facfe;
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .panel-section h3 i {
      background: rgba(79, 172, 254, 0.2);
      width: 36px;
      height: 36px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 50%;
    }

    .btn-group {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
      gap: 10px;
      margin-bottom: 15px;
    }

    .btn {
      background: linear-gradient(to right, var(--primary), var(--secondary));
      color: white;
      border: none;
      padding: 10px 15px;
      border-radius: 6px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      transition: all 0.3s ease;
      font-weight: 500;
      text-align: center;
    }

    .btn:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
      opacity: 0.9;
    }

    .btn.active {
      box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.5);
    }

    .btn-success {
      background: linear-gradient(to right, var(--success), #66bb6a);
    }

    .btn-warning {
      background: linear-gradient(to right, var(--warning), #ffa726);
    }

    .btn-danger {
      background: linear-gradient(to right, var(--danger), #ef5350);
    }

    .feature-list {
      max-height: 200px;
      overflow-y: auto;
      background: rgba(40, 50, 70, 0.6);
      border-radius: 8px;
      padding: 10px;
      margin-bottom: 15px;
    }

    .feature-item {
      padding: 10px;
      margin-bottom: 8px;
      background: rgba(50, 60, 80, 0.5);
      border-radius: 6px;
      cursor: pointer;
      transition: all 0.2s ease;
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .feature-item:hover {
      background: rgba(60, 70, 100, 0.7);
    }

    .feature-item.active {
      background: rgba(79, 172, 254, 0.3);
      border-left: 3px solid #4facfe;
    }

    .feature-icon {
      width: 24px;
      height: 24px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 50%;
      background: rgba(79, 172, 254, 0.3);
    }

    .feature-info {
      flex: 1;
    }

    .feature-name {
      font-weight: 500;
    }

    .feature-type {
      font-size: 0.8rem;
      opacity: 0.8;
    }

    .form-group {
      margin-bottom: 15px;
    }

    .form-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: 500;
    }

    .form-control {
      width: 100%;
      padding: 10px;
      border-radius: 6px;
      background: rgba(40, 50, 70, 0.9);
      border: 1px solid rgba(255, 255, 255, 0.2);
      color: white;
    }

    .form-control:focus {
      outline: none;
      border-color: #4facfe;
    }

    .color-picker {
      display: flex;
      gap: 10px;
      align-items: center;
    }

    .color-preview {
      width: 30px;
      height: 30px;
      border-radius: 50%;
      border: 2px solid white;
    }

    .export-section {
      margin-top: auto;
      padding-top: 20px;
      border-top: 1px solid rgba(255, 255, 255, 0.1);
    }

    .json-viewer {
      background: rgba(40, 50, 70, 0.7);
      border-radius: 8px;
      padding: 15px;
      font-family: monospace;
      white-space: pre-wrap;
      max-height: 200px;
      overflow-y: auto;
      margin-top: 15px;
      font-size: 0.9rem;
    }

    footer {
      text-align: center;
      margin-top: 20px;
      padding: 20px;
      font-size: 0.9rem;
      opacity: 0.8;
    }

    @media (max-width: 992px) {
      .app-container {
        grid-template-columns: 1fr;
        height: auto;
      }

      #map {
        height: 500px;
      }
    }
  </style>
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/css/ol.css">
</head>

<body>
  <div class="container">
    <header>
      <h1><i class="fas fa-draw-polygon"></i> OpenLayers 几何图形编辑组件</h1>
      <p class="subtitle">封装点、线、面绘制、编辑、保存、修改和导出功能</p>
    </header>

    <div class="app-container">
      <div id="map"></div>

      <div class="control-panel">
        <div class="panel-section">
          <h3><i class="fas fa-tools"></i> 绘制工具</h3>
          <div class="btn-group">
            <button id="point-btn" class="btn"><i class="fas fa-map-marker-alt"></i> 点</button>
            <button id="line-btn" class="btn"><i class="fas fa-grip-lines"></i> 线</button>
            <button id="polygon-btn" class="btn"><i class="fas fa-draw-polygon"></i> 面</button>
            <button id="edit-btn" class="btn"><i class="fas fa-edit"></i> 编辑</button>
          </div>

          <div class="btn-group">
            <button id="clear-btn" class="btn btn-danger"><i class="fas fa-trash-alt"></i> 清除</button>
            <button id="save-btn" class="btn btn-success"><i class="fas fa-save"></i> 保存</button>
          </div>
        </div>

        <div class="panel-section">
          <h3><i class="fas fa-palette"></i> 样式设置</h3>
          <div class="form-group">
            <label>点样式</label>
            <div class="color-picker">
              <div class="color-preview" id="point-color-preview" style="background-color: #ff5252;"></div>
              <input type="color" id="point-color" class="form-control" value="#ff5252">
            </div>
          </div>

          <div class="form-group">
            <label>点半径</label>
            <input type="range" id="point-radius" class="form-control" min="5" max="20" value="10">
            <div class="text-end"><span id="radius-value">10</span> 像素</div>
          </div>

          <div class="form-group">
            <label>线样式</label>
            <div class="color-picker">
              <div class="color-preview" id="line-color-preview" style="background-color: #4caf50;"></div>
              <input type="color" id="line-color" class="form-control" value="#4caf50">
            </div>
          </div>

          <div class="form-group">
            <label>线宽度</label>
            <input type="range" id="line-width" class="form-control" min="1" max="10" value="3">
            <div class="text-end"><span id="width-value">3</span> 像素</div>
          </div>

          <div class="form-group">
            <label>面填充色</label>
            <div class="color-picker">
              <div class="color-preview" id="fill-color-preview" style="background-color: #2196f3;"></div>
              <input type="color" id="fill-color" class="form-control" value="#2196f3">
            </div>
          </div>

          <div class="form-group">
            <label>面边框颜色</label>
            <div class="color-picker">
              <div class="color-preview" id="stroke-color-preview" style="background-color: #1565c0;"></div>
              <input type="color" id="stroke-color" class="form-control" value="#1565c0">
            </div>
          </div>

          <button id="apply-style" class="btn"><i class="fas fa-paint-brush"></i> 应用样式</button>
        </div>

        <div class="panel-section">
          <h3><i class="fas fa-layer-group"></i> 要素管理</h3>
          <div class="feature-list" id="feature-list">
            <!-- 要素列表将通过JavaScript动态生成 -->
          </div>

          <div class="btn-group">
            <button id="rename-btn" class="btn"><i class="fas fa-tag"></i> 重命名</button>
            <button id="delete-btn" class="btn btn-danger"><i class="fas fa-trash"></i> 删除</button>
          </div>
        </div>

        <div class="export-section">
          <h3><i class="fas fa-file-export"></i> 数据操作</h3>
          <div class="btn-group">
            <button id="export-btn" class="btn btn-success"><i class="fas fa-download"></i> 导出GeoJSON</button>
            <button id="import-btn" class="btn"><i class="fas fa-upload"></i> 导入数据</button>
          </div>

          <div class="json-viewer" id="json-viewer">
            {/* GeoJSON预览将显示在这里 */}
          </div>
        </div>
      </div>
    </div>

    <footer>
      <p>OpenLayers 几何图形编辑组件 | 封装绘制、编辑、保存、修改和导出功能</p>
    </footer>
  </div>

  <!-- 加载OpenLayers库 -->
  <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.15.1/build/ol.js"></script>

  <script>
    // 几何图形编辑组件封装
    class GeometryEditor {
      constructor(mapId) {
        this.mapId = mapId;
        this.map = null;
        this.vectorLayer = null;
        this.drawInteraction = null;
        this.modifyInteraction = null;
        this.snapInteraction = null;
        this.selectedFeature = null;
        this.currentMode = null;
        this.defaultStyles = {
          point: {
            color: '#ff5252',
            radius: 10
          },
          line: {
            color: '#4caf50',
            width: 3
          },
          polygon: {
            fill: '#2196f3',
            stroke: '#1565c0'
          }
        };

        this.initMap();
        this.initControls();
      }

      // 初始化地图
      initMap() {
        this.map = new ol.Map({
          target: this.mapId,
          layers: [
            new ol.layer.Tile({
              source: new ol.source.OSM()
            })
          ],
          view: new ol.View({
            center: ol.proj.fromLonLat([116.4, 39.9]),
            zoom: 12
          })
        });

        // 创建矢量图层
        this.vectorLayer = new ol.layer.Vector({
          source: new ol.source.Vector(),
          style: (feature) => {
            return this.getFeatureStyle(feature);
          }
        });
        this.map.addLayer(this.vectorLayer);

        // 添加选择交互
        this.selectInteraction = new ol.interaction.Select({
          layers: [this.vectorLayer],
          style: null
        });
        this.map.addInteraction(this.selectInteraction);

        // 监听选择变化
        this.selectInteraction.on('select', (event) => {
          if (event.selected.length > 0) {
            this.selectFeature(event.selected[0]);
          } else {
            this.deselectFeature();
          }
        });
      }

      // 初始化UI控件
      initControls() {
        // 绘制按钮事件
        document.getElementById('point-btn').addEventListener('click', () => this.startDrawing('Point'));
        document.getElementById('line-btn').addEventListener('click', () => this.startDrawing('LineString'));
        document.getElementById('polygon-btn').addEventListener('click', () => this.startDrawing('Polygon'));
        document.getElementById('edit-btn').addEventListener('click', () => this.toggleEditMode());
        document.getElementById('clear-btn').addEventListener('click', () => this.clearFeatures());
        document.getElementById('save-btn').addEventListener('click', () => this.saveFeatures());
        document.getElementById('apply-style').addEventListener('click', () => this.applyStyle());
        document.getElementById('rename-btn').addEventListener('click', () => this.renameFeature());
        document.getElementById('delete-btn').addEventListener('click', () => this.deleteFeature());
        document.getElementById('export-btn').addEventListener('click', () => this.exportGeoJSON());
        document.getElementById('import-btn').addEventListener('click', () => this.importData());

        // 样式控制事件
        document.getElementById('point-color').addEventListener('input', (e) => {
          document.getElementById('point-color-preview').style.backgroundColor = e.target.value;
        });

        document.getElementById('line-color').addEventListener('input', (e) => {
          document.getElementById('line-color-preview').style.backgroundColor = e.target.value;
        });

        document.getElementById('fill-color').addEventListener('input', (e) => {
          document.getElementById('fill-color-preview').style.backgroundColor = e.target.value;
        });

        document.getElementById('stroke-color').addEventListener('input', (e) => {
          document.getElementById('stroke-color-preview').style.backgroundColor = e.target.value;
        });

        document.getElementById('point-radius').addEventListener('input', (e) => {
          document.getElementById('radius-value').textContent = e.target.value;
        });

        document.getElementById('line-width').addEventListener('input', (e) => {
          document.getElementById('width-value').textContent = e.target.value;
        });

        // 创建示例数据
        this.createSampleFeatures();
      }

      // 开始绘图
      startDrawing(type) {
        this.deactivateEditMode();

        if (this.drawInteraction) {
          this.map.removeInteraction(this.drawInteraction);
        }

        this.drawInteraction = new ol.interaction.Draw({
          source: this.vectorLayer.getSource(),
          type: type
        });

        this.map.addInteraction(this.drawInteraction);

        this.drawInteraction.on('drawend', (event) => {
          const feature = event.feature;
          const featureName = `${this.getTypeName(type)}-${new Date().getTime().toString().slice(-4)}`;

          feature.set('name', featureName);
          feature.set('type', type);

          this.updateFeatureList();
          this.selectFeature(feature);

          this.map.removeInteraction(this.drawInteraction);
          this.drawInteraction = null;
        });
      }

      // 切换编辑模式
      toggleEditMode() {
        if (this.modifyInteraction) {
          this.deactivateEditMode();
        } else {
          this.activateEditMode();
        }
      }

      // 激活编辑模式
      activateEditMode() {
        if (!this.modifyInteraction) {
          this.modifyInteraction = new ol.interaction.Modify({
            source: this.vectorLayer.getSource()
          });
          this.map.addInteraction(this.modifyInteraction);

          this.snapInteraction = new ol.interaction.Snap({
            source: this.vectorLayer.getSource()
          });
          this.map.addInteraction(this.snapInteraction);

          document.getElementById('edit-btn').classList.add('active');
        }
      }

      // 关闭编辑模式
      deactivateEditMode() {
        if (this.modifyInteraction) {
          this.map.removeInteraction(this.modifyInteraction);
          this.map.removeInteraction(this.snapInteraction);
          this.modifyInteraction = null;
          this.snapInteraction = null;

          document.getElementById('edit-btn').classList.remove('active');
        }
      }

      // 选择要素
      selectFeature(feature) {
        // 清除之前的选择
        if (this.selectedFeature) {
          document.querySelector(`.feature-item[data-id="${this.selectedFeature.ol_uid}"]`)
            ?.classList.remove('active');
        }

        this.selectedFeature = feature;

        // 更新要素列表选中状态
        document.querySelector(`.feature-item[data-id="${feature.ol_uid}"]`)
          ?.classList.add('active');

        // 更新样式控制面板
        this.updateStyleControls(feature);
      }

      // 取消选择要素
      deselectFeature() {
        if (this.selectedFeature) {
          document.querySelector(`.feature-item[data-id="${this.selectedFeature.ol_uid}"]`)
            ?.classList.remove('active');
          this.selectedFeature = null;
        }
      }

      // 更新样式控制面板
      updateStyleControls(feature) {
        if (!feature) return;

        const type = feature.get('type');
        const style = this.getFeatureStyle(feature);

        if (type === 'Point') {
          const circleStyle = style.getImage();
          document.getElementById('point-color').value = ol.color.asString(circleStyle.getFill().getColor()).toLowerCase();
          document.getElementById('point-color-preview').style.backgroundColor = ol.color.asString(circleStyle.getFill().getColor());
          document.getElementById('point-radius').value = circleStyle.getRadius();
          document.getElementById('radius-value').textContent = circleStyle.getRadius();
        }

        if (type === 'LineString') {
          const strokeStyle = style.getStroke();
          document.getElementById('line-color').value = ol.color.asString(strokeStyle.getColor()).toLowerCase();
          document.getElementById('line-color-preview').style.backgroundColor = ol.color.asString(strokeStyle.getColor());
          document.getElementById('line-width').value = strokeStyle.getWidth();
          document.getElementById('width-value').textContent = strokeStyle.getWidth();
        }

        if (type === 'Polygon') {
          const fillStyle = style.getFill();
          const strokeStyle = style.getStroke();

          if (fillStyle) {
            document.getElementById('fill-color').value = ol.color.asString(fillStyle.getColor()).toLowerCase();
            document.getElementById('fill-color-preview').style.backgroundColor = ol.color.asString(fillStyle.getColor());
          }

          if (strokeStyle) {
            document.getElementById('stroke-color').value = ol.color.asString(strokeStyle.getColor()).toLowerCase();
            document.getElementById('stroke-color-preview').style.backgroundColor = ol.color.asString(strokeStyle.getColor());
          }
        }
      }

      // 应用样式到选中的要素
      applyStyle() {
        if (!this.selectedFeature) {
          alert('请先选择一个要素！');
          return;
        }

        const type = this.selectedFeature.get('type');
        let newStyle;

        switch (type) {
          case 'Point':
            const pointRadius = parseInt(document.getElementById('point-radius').value);
            const pointColor = document.getElementById('point-color').value;
            newStyle = this.createPointStyle(pointColor, pointRadius);
            break;

          case 'LineString':
            const lineWidth = parseInt(document.getElementById('line-width').value);
            const lineColor = document.getElementById('line-color').value;
            newStyle = this.createLineStyle(lineColor, lineWidth);
            break;

          case 'Polygon':
            const fillColor = document.getElementById('fill-color').value;
            const strokeColor = document.getElementById('stroke-color').value;
            newStyle = this.createPolygonStyle(fillColor, strokeColor);
            break;
        }

        if (newStyle) {
          this.selectedFeature.set('style', newStyle);
          this.vectorLayer.changed(); // 强制刷新

          // 更新要素列表中的预览
          const listItem = document.querySelector(`.feature-item[data-id="${this.selectedFeature.ol_uid}"]`);
          if (listItem) {
            const icon = listItem.querySelector('.feature-icon');
            if (icon) {
              switch (type) {
                case 'Point':
                  icon.style.backgroundColor = pointColor;
                  break;
                case 'LineString':
                  icon.style.backgroundColor = lineColor;
                  break;
                case 'Polygon':
                  icon.style.backgroundColor = fillColor;
                  break;
              }
            }
          }
        }
      }

      // 创建点样式
      createPointStyle(color, radius = 10) {
        return new ol.style.Style({
          image: new ol.style.Circle({
            radius: radius,
            fill: new ol.style.Fill({ color: color }),
            stroke: new ol.style.Stroke({
              color: '#fff', width: 2
            })
          })
        });
      }

      // 创建线样式
      createLineStyle(color, width = 3) {
        return new ol.style.Style({
          stroke: new ol.style.Stroke({
            color: color,
            width: width
          })
        });
      }

      // 创建面样式
      createPolygonStyle(fillColor, strokeColor) {
        return new ol.style.Style({
          fill: new ol.style.Fill({
            color: fillColor
          }),
          stroke: new ol.style.Stroke({
            color: strokeColor,
            width: 2
          })
        });
      }

      // 获取要素样式
      getFeatureStyle(feature) {
        // 如果要素有自定义样式，使用自定义样式
        if (feature.get('style')) {
          return feature.get('style');
        }

        // 否则使用默认样式
        const type = feature.get('type');

        switch (type) {
          case 'Point':
            return this.createPointStyle(
              this.defaultStyles.point.color,
              this.defaultStyles.point.radius
            );
          case 'LineString':
            return this.createLineStyle(
              this.defaultStyles.line.color,
              this.defaultStyles.line.width
            );
          case 'Polygon':
            return this.createPolygonStyle(
              this.defaultStyles.polygon.fill,
              this.defaultStyles.polygon.stroke
            );
          default:
            return new ol.style.Style({
              image: new ol.style.Circle({
                radius: 8,
                fill: new ol.style.Fill({ color: '#ff9800' })
              })
            });
        }
      }

      // 更新要素列表
      updateFeatureList() {
        const features = this.vectorLayer.getSource().getFeatures();
        const list = document.getElementById('feature-list');
        list.innerHTML = '';

        features.forEach(feature => {
          const item = document.createElement('div');
          item.className = 'feature-item';
          item.dataset.id = feature.ol_uid;

          const type = feature.get('type');
          const style = this.getFeatureStyle(feature);
          let color = '#ccc';

          if (type === 'Point') {
            color = style.getImage().getFill().getColor();
          } else if (type === 'LineString') {
            color = style.getStroke().getColor();
          } else if (type === 'Polygon') {
            color = style.getFill().getColor();
          }

          item.innerHTML = `
                        <div class="feature-icon" style="background-color: ${ol.color.asString(color)};">
                            <i class="fas ${this.getFeatureIcon(type)}"></i>
                        </div>
                        <div class="feature-info">
                            <div class="feature-name">${feature.get('name')}</div>
                            <div class="feature-type">${this.getTypeName(type)}</div>
                        </div>
                    `;

          item.addEventListener('click', () => {
            this.selectFeature(feature);
          });

          list.appendChild(item);
        });
      }

      // 获取要素图标
      getFeatureIcon(type) {
        switch (type) {
          case 'Point': return 'fa-map-marker-alt';
          case 'LineString': return 'fa-grip-lines';
          case 'Polygon': return 'fa-draw-polygon';
          default: return 'fa-shapes';
        }
      }

      // 获取类型名称
      getTypeName(type) {
        switch (type) {
          case 'Point': return '点要素';
          case 'LineString': return '线要素';
          case 'Polygon': return '面要素';
          default: return '未知要素';
        }
      }

      // 重命名要素
      renameFeature() {
        if (!this.selectedFeature) {
          alert('请先选择一个要素！');
          return;
        }

        const newName = prompt('输入新的要素名称：', this.selectedFeature.get('name'));
        if (newName) {
          this.selectedFeature.set('name', newName);
          this.updateFeatureList();
          this.selectFeature(this.selectedFeature);
        }
      }

      // 删除要素
      deleteFeature() {
        if (!this.selectedFeature) {
          alert('请先选择一个要素！');
          return;
        }

        if (confirm('确定要删除这个要素吗？')) {
          this.vectorLayer.getSource().removeFeature(this.selectedFeature);
          this.selectedFeature = null;
          this.updateFeatureList();
        }
      }

      // 清除所有要素
      clearFeatures() {
        if (confirm('确定要清除所有要素吗？')) {
          this.vectorLayer.getSource().clear();
          this.selectedFeature = null;
          this.updateFeatureList();
        }
      }

      // 保存要素数据
      saveFeatures() {
        const geoJson = this.getGeoJSON();
        localStorage.setItem('ol_features', JSON.stringify(geoJson));
        alert('要素数据已保存到本地存储！');

        // 更新JSON预览
        document.getElementById('json-viewer').textContent =
          JSON.stringify(geoJson, null, 2);
      }

      // 获取GeoJSON数据
      getGeoJSON() {
        const format = new ol.format.GeoJSON();
        return format.writeFeatures(
          this.vectorLayer.getSource().getFeatures(),
          { featureProjection: 'EPSG:3857' }
        );
      }

      // 导出GeoJSON文件
      exportGeoJSON() {
        const geoJson = this.getGeoJSON();
        const blob = new Blob([JSON.stringify(geoJson, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = `ol_features_${new Date().toISOString().slice(0, 10)}.geojson`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
      }

      // 导入数据
      importData() {
        const data = prompt('粘贴GeoJSON数据：');
        if (!data) return;

        try {
          const geoJson = JSON.parse(data);
          const format = new ol.format.GeoJSON();
          const features = format.readFeatures(geoJson, {
            featureProjection: 'EPSG:3857'
          });

          this.vectorLayer.getSource().addFeatures(features);
          this.updateFeatureList();
          alert('数据导入成功！');
        } catch (e) {
          alert('导入失败：无效的GeoJSON格式');
          console.error(e);
        }
      }

      // 创建示例要素
      createSampleFeatures() {
        // 点要素示例
        const pointFeature = new ol.Feature({
          geometry: new ol.geom.Point(ol.proj.fromLonLat([116.4, 39.92]))
        });
        pointFeature.set('name', '天安门');
        pointFeature.set('type', 'Point');

        // 线要素示例
        const lineCoords = [
          ol.proj.fromLonLat([116.3, 39.95]),
          ol.proj.fromLonLat([116.35, 39.94]),
          ol.proj.fromLonLat([116.4, 39.92])
        ];
        const lineFeature = new ol.Feature({
          geometry: new ol.geom.LineString(lineCoords)
        });
        lineFeature.set('name', '长安街');
        lineFeature.set('type', 'LineString');

        // 面要素示例
        const polygonCoords = [
          ol.proj.fromLonLat([116.38, 39.93]),
          ol.proj.fromLonLat([116.42, 39.93]),
          ol.proj.fromLonLat([116.42, 39.91]),
          ol.proj.fromLonLat([116.38, 39.91]),
          ol.proj.fromLonLat([116.38, 39.93])
        ];
        const polygonFeature = new ol.Feature({
          geometry: new ol.geom.Polygon([polygonCoords])
        });
        polygonFeature.set('name', '故宫区域');
        polygonFeature.set('type', 'Polygon');

        // 添加到图层
        this.vectorLayer.getSource().addFeatures([
          pointFeature, lineFeature, polygonFeature
        ]);

        // 更新要素列表
        this.updateFeatureList();

        // 调整视图
        const extent = this.vectorLayer.getSource().getExtent();
        this.map.getView().fit(extent, {
          padding: [50, 50, 50, 50],
          duration: 1000
        });
      }
    }

    // 页面加载完成后初始化编辑器
    window.addEventListener('load', () => {
      const editor = new GeometryEditor('map');
    });
  </script>
</body>

</html>